﻿using System;
using Firefly.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Firefly.Framework.Components.Terrain
{
    using Firefly.Framework;

    public class VertexMap : IDisposableEx
    {
        public VertexPosition[][] Verticies;
        public VertexBuffer[] VertexBuffers;
        public int[][] Indices;
        public DynamicIndexBuffer[] DynamicIndexBuffers;
        private int _sqrtNumberOfVertexBuffers;
        private int _vertexBufferCount;

        public int BufferSize { get; private set; }
        public int[] RenderedIndicies { get; set; }

        public VertexMap(HeightMap heightMap, int bufferSize, float verticalScale, float horizontalScale)
        {
            BufferSize = bufferSize;
            InitializeSize(heightMap.Height);
            InitializeBuffers();
            InitializeArrays();
            GenerateVertexPositions(heightMap.Width, heightMap.Height, verticalScale, horizontalScale, heightMap);
            InitializeDynamicIndexBuffers();
            RenderedIndicies = new int[_vertexBufferCount + 1];
        }

        public void ResetRenderedIndicies()
        {
            for (int i = 0; i < RenderedIndicies.Length; i++)
            {
                RenderedIndicies[i] = 0;
            }
        }

        public void UpdateIndexBuffers()
        {
            for (int i = 0; i < DynamicIndexBuffers.Length; i++)
            {
                if (RenderedIndicies[i] > 0)
                {
                    DynamicIndexBuffers[i].SetData<int>(0, Indices[i], 0, RenderedIndicies[i], SetDataOptions.Discard);
                }
            }
        }

        public void DrawIndexedPrimitives()
        {
            for (int i = 0; i < Verticies.Length; i++)
            {
                GraphicsService.Instance.Indices = DynamicIndexBuffers[i];
                GraphicsService.Instance.Vertices[0].SetSource(VertexBuffers[i], 0, VertexPosition.SizeInBytes);
               
                if (RenderedIndicies[i] > 0)
                {
                    GraphicsService.Instance.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, Verticies[i].Length, 0, RenderedIndicies[i] / 3);
                }

                GraphicsService.Instance.Indices = null;
            }
        }

        private void InitializeSize(int height)
        {
            _vertexBufferCount = (int)Math.Pow((height - 1) / (BufferSize - 1), 2);
            _sqrtNumberOfVertexBuffers = (int)Math.Sqrt(_vertexBufferCount);
        }

        private void InitializeBuffers()
        {
            Verticies = new VertexPosition[_vertexBufferCount + 1][];
            VertexBuffers = new VertexBuffer[_vertexBufferCount + 1];
            Indices = new int[_vertexBufferCount + 1][];
        }

        private void InitializeArrays()
        {
            for (int i = 0; i < _vertexBufferCount + 1; i++)
            {
                Verticies[i] = new VertexPosition[(BufferSize + 1) * (BufferSize + 1)];
                VertexBuffers[i] = new VertexBuffer(GraphicsService.Instance.GraphicsDevice, (BufferSize + 1) * (BufferSize + 1) * VertexPosition.SizeInBytes, BufferUsage.WriteOnly);
                Indices[i] = new int[BufferSize * BufferSize * 6];
            }
        }

        private void GenerateVertexPositions(int width, int height, float verticalScale, float horizontalScale, HeightMap heightMap)
        {
            int xBufferIndex;
            int yBufferIndex;
            int xYBufferIndex;
            int bufferIndex;

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    xYBufferIndex = (int)(Math.Floor(((float)(x + 1) / width) * _sqrtNumberOfVertexBuffers) + (Math.Floor(((float)(y + 1) / width) * _sqrtNumberOfVertexBuffers) * _sqrtNumberOfVertexBuffers));
                    xBufferIndex = (int)(Math.Floor(((float)(x + 1) / width) * _sqrtNumberOfVertexBuffers) + (Math.Floor(((float)y / width) * _sqrtNumberOfVertexBuffers) * _sqrtNumberOfVertexBuffers));
                    yBufferIndex = (int)(Math.Floor(((float)x / width) * _sqrtNumberOfVertexBuffers) + (Math.Floor(((float)(y + 1) / width) * _sqrtNumberOfVertexBuffers) * _sqrtNumberOfVertexBuffers));
                    bufferIndex = (int)(Math.Floor(((float)x / width) * _sqrtNumberOfVertexBuffers) + (Math.Floor(((float)y / width) * _sqrtNumberOfVertexBuffers) * _sqrtNumberOfVertexBuffers));

                    // Fill Vertex Arrays.
                    for (int i = 0; i < _vertexBufferCount; i++)
                    {
                        // This works to sort the VB's.  It works to align the x,y and VBi values: only if an x/y coord is in an i VB. 
                        // Uses 1st VB chart. Just use i + 1.  This is the 'multiple vertex buffers' algorithm, which puts vertices 
                        // into their own VBuffers.
                        if (xYBufferIndex == i)
                        {
                            if (x < height - 1)
                            {
                                Verticies[i + 1][(x % (BufferSize - 1)) + ((y % (BufferSize - 1)) * BufferSize)].Position = new Vector3(x * horizontalScale, heightMap[x, y] * verticalScale, y * horizontalScale);
                            }
                        }
                        else
                        {
                            // Bottom or left.
                            if (bufferIndex == i)
                            {
                                if (yBufferIndex == i)
                                {
                                    Verticies[i + 1][((x % (BufferSize - 1)) + (BufferSize - 1)) + ((y % (BufferSize - 1)) * BufferSize)].Position = new Vector3(x * horizontalScale, heightMap[x, y] * verticalScale, y * horizontalScale);
                                }
                                else
                                {
                                    if (xBufferIndex == i)
                                    {
                                        Verticies[i + 1][(x % (BufferSize - 1)) + (((y % (BufferSize - 1)) + (BufferSize - 1)) * BufferSize)].Position = new Vector3(x * horizontalScale, heightMap[x, y] * verticalScale, y * horizontalScale);
                                    }
                                    else
                                    {
                                        Verticies[i + 1][(x % (BufferSize - 1)) + (BufferSize - 1) + (((y % (BufferSize - 1)) + (BufferSize - 1)) * BufferSize)].Position = new Vector3(x * horizontalScale, heightMap[x, y] * verticalScale, y * horizontalScale);
                                    }
                                }
                            }
                            else
                            {
                                // Corner Left.
                                if (yBufferIndex == i)
                                {
                                    if (y < height - 1)
                                    {
                                        Verticies[i + 1][((x % (BufferSize - 1)) + (BufferSize - 1) + ((y % (BufferSize - 1)) * BufferSize))].Position = new Vector3(x * horizontalScale, heightMap[x, y] * verticalScale, y * horizontalScale);
                                    }
                                }

                                // Corner Bottom.
                                if (xBufferIndex == i)
                                {
                                    if (x < height - 1)
                                    {
                                        Verticies[i + 1][(x % (BufferSize - 1)) + (((y % (BufferSize - 1)) + (BufferSize - 1)) * BufferSize)].Position = new Vector3(x * horizontalScale, heightMap[x, y] * verticalScale, y * horizontalScale);
                                    }
                                }
                            }
                        }

                        if (i == 0)
                        {
                            int stepSize = (width - 1) / (BufferSize - 1);
                            int numberOfSteps = (width - 1) / stepSize;

                            if (x % stepSize == 0 && y % stepSize == 0)
                            {
                                Verticies[0][(x / stepSize) + ((y / stepSize) * BufferSize)].Position = new Vector3(x * horizontalScale, heightMap[x, y] * verticalScale, y * horizontalScale);
                            }
                        }
                    }
                }
            }

            for (int j = 0; j < _vertexBufferCount + 1; j++)
            {
                VertexBuffers[j].SetData<VertexPosition>(Verticies[j]);
            }
        }

        private void InitializeDynamicIndexBuffers()
        {
            DynamicIndexBuffers = new DynamicIndexBuffer[Indices.Length];

            for (int l = 0; l < Indices.Length; l++)
            {
                DynamicIndexBuffers[l] = new DynamicIndexBuffer(GraphicsService.Instance.GraphicsDevice, Verticies[l].Length * 32, BufferUsage.WriteOnly, IndexElementSize.ThirtyTwoBits);
            }
        }

        #region IDisposableEx Members

        public bool Disposed { get; private set; }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                if (disposing)
                {
                    if (VertexBuffers != null && VertexBuffers.Length > 0)
                    {
                        for (int i = 0; i < VertexBuffers.Length; i++)
                        {
                            VertexBuffers[i].Dispose();
                            VertexBuffers[i] = null;
                        }
                    }
                }

                Disposed = true;
            }
        }

        ~VertexMap()
        {
            Dispose(false);
        }

        #endregion
    }
}